package pl.maciejziarko.mshare.web.mvc;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresGuest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import pl.maciejziarko.mshare.domain.Account;
import pl.maciejziarko.mshare.dto.LoginDTO;
import pl.maciejziarko.mshare.dto.RegisterDTO;
import pl.maciejziarko.mshare.dto.converters.RegisterDTOConverter;
import pl.maciejziarko.mshare.security.SecurityService;
import pl.maciejziarko.mshare.service.AccountService;
import pl.maciejziarko.mshare.service.exception.AccountNotFoundException;
import pl.maciejziarko.mshare.service.exception.AccountServiceException;
import pl.maciejziarko.mshare.service.exception.EmailUsedException;
import pl.maciejziarko.mshare.service.exception.UsernameTakenException;

import javax.validation.Valid;

/**
 * @author Maciej Ziarko
 */
@Controller
public class AccountController {

    private static final Logger LOGGER = LoggerFactory.getLogger(AccountController.class);

    @Autowired
    private SecurityService securityService;

    @Autowired
    private AccountService accountService;

    @Autowired
    private RegisterDTOConverter registerDTOConverter;

    public AccountController() {
    }

    @Autowired
    public AccountController(SecurityService securityService,
                             AccountService accountService,
                             RegisterDTOConverter registerDTOConverter) {
        this.securityService = securityService;
        this.accountService = accountService;
        this.registerDTOConverter = registerDTOConverter;
    }

    @RequiresAuthentication
    @RequestMapping(value = "/password", method = RequestMethod.GET)
    public String passwordGet(Model model) {
        return View.PASSWORD;
    }

    @RequiresAuthentication
    @RequestMapping(value = "/profile", method = RequestMethod.GET)
    public String profileGet(Model model) {
        String username = securityService.getUsername();

        try {
            Account account = accountService.getAccount(username);
            RegisterDTO registerDTO = registerDTOConverter.convert(account);
            model.addAttribute(registerDTO);
        } catch (AccountNotFoundException | AccountServiceException e) {
            return Redirect.toLocation(Redirect.APP_ERROR, model);
        }

        return View.PROFILE;
    }
    
    @RequiresAuthentication
    @RequestMapping(value = "/profile", method = RequestMethod.POST)
    public String profilePost(Model model, @Valid RegisterDTO registerForm,
                              BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            LOGGER.debug("There are profile form errors.");

            return View.PROFILE;
        }

        LOGGER.debug(registerForm.toString());

        Account account = registerDTOConverter.convert(registerForm);

        try {
            accountService.editAccount(account);

            return View.PROFILE;
        } catch (AccountServiceException e) {
            bindingResult.addError(new NicerFieldError<String>(
                    "registerDTO",
                    "general",
                    "Application error occurred! Try again."
            ));
        } catch (EmailUsedException e) {
            bindingResult.addError(new NicerFieldError<String>(
                    "registerDTO",
                    "email",
                    account.getEmail(),
                    "Email already used!"
            ));
        }
        return View.PROFILE;
    }

    @RequiresGuest
    @RequestMapping(value = "/register", method = RequestMethod.GET)
    public String registerGet(Model model) {

        model.addAttribute(new RegisterDTO());

        return View.REGISTER;
    }

    @RequiresGuest
    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String registerPost(Model model, @Valid RegisterDTO registerForm,
                               BindingResult bindingResult) {

        if (bindingResult.hasErrors()) {
            LOGGER.debug("There are register form errors.");

            return View.REGISTER;
        }

        Account account = registerDTOConverter.convert(registerForm);

        try {
            accountService.addAccount(account);
            return Redirect.toLocation(Redirect.LOGIN, model);
        } catch (UsernameTakenException e) {
            bindingResult.addError(new NicerFieldError<String>(
                    "registerDTO",
                    "username",
                    account.getUsername(),
                    "Username already taken!"
            ));
        } catch (EmailUsedException e) {
            bindingResult.addError(new NicerFieldError<String>(
                    "registerDTO",
                    "email",
                    account.getEmail(),
                    "Email already used!"
            ));
        } catch (AccountServiceException e) {
            return Redirect.toLocation(Redirect.APP_ERROR);
        }
        return View.REGISTER;
    }

    @RequiresGuest
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String loginGet(Model model) {

        model.addAttribute(new LoginDTO());

        return View.LOGIN;
    }

    @RequiresGuest
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String loginPost(Model model, @Valid LoginDTO loginDTO,
                            BindingResult bindingResult) {

        if (bindingResult.hasErrors()) {
            LOGGER.debug("There are login form errors.");

            return View.LOGIN;
        }

        AuthenticationToken authenticationToken = new UsernamePasswordToken(loginDTO.getUsername(),
                loginDTO.getPassword());

        try {
            LOGGER.debug("Trying to login.");

            securityService.login(authenticationToken);

            LOGGER.debug("Login action successful.");

            return Redirect.toLocation(Redirect.HOME, model);

            //Failures:
        } catch (UnknownAccountException e) {

            LOGGER.debug("UnknownAccountException: {}", e.getMessage());

            bindingResult.addError(
                    new NicerFieldError<String>(
                            "authenticationToken",
                            "username",
                            (String) authenticationToken.getPrincipal(),
                            "Account not found!")
            );

        } catch (AuthenticationException e) {

            LOGGER.debug("AuthenticationException: {}", e.getMessage());

            bindingResult.addError(
                    new NicerFieldError<String>(
                            "authenticationToken",
                            "password",
                            "Invalid password!")
            );
        }

        return View.LOGIN;
    }

    @ExceptionHandler(AuthorizationException.class)
    public String handleAuthorizationException() {
        return Redirect.toLocation(Redirect.HOME);
    }

}
